package com.alibaba.nacos.naming.push.v2.task;

import com.alibaba.nacos.api.naming.pojo.ServiceInfo;
import com.alibaba.nacos.common.task.AbstractExecuteTask;
import com.alibaba.nacos.naming.core.v2.client.Client;
import com.alibaba.nacos.naming.core.v2.client.manager.ClientManager;
import com.alibaba.nacos.naming.core.v2.index.ClientServiceIndexesManager;
import com.alibaba.nacos.naming.core.v2.index.ServiceStorage;
import com.alibaba.nacos.naming.core.v2.metadata.NamingMetadataManager;
import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata;
import com.alibaba.nacos.naming.core.v2.pojo.Service;
import com.alibaba.nacos.naming.pojo.Subscriber;
import com.alibaba.nacos.naming.push.v2.PushDataWrapper;
import com.alibaba.nacos.naming.push.v2.executor.PushExecutor;
import com.alibaba.nacos.naming.push.v2.executor.PushExecutorRpcImpl;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import java.util.Collection;

@Slf4j
@Component
@Scope("prototype")
public class PushExecuteTask
        extends AbstractExecuteTask {
    @Autowired
    private ServiceStorage serviceStorage;
    @Autowired
    private NamingMetadataManager namingMetadataManager;
    @Autowired
    private ClientManager clientManager;
    @Autowired
    private ClientServiceIndexesManager clientServiceIndexesManager;
    @Autowired
    private PushExecutorRpcImpl pushExecutor;

    private Service service;
    private PushDelayTask pushDelayTask;

    public PushExecuteTask(Service service,
                           PushDelayTask pushDelayTask) {
        this.service = service;
        this.pushDelayTask = pushDelayTask;
    }

    @Override
    public void run() {
        try {
            // 组装需要发送的信息
            PushDataWrapper pushDataWrapper = generatePushData();
            // 从订阅列表中获取待通知的客户端id
            Collection<String> targetClientIds = getTargetClientIds();
            for (String clientId : targetClientIds) {
                Client client = clientManager.getClient(clientId);
                if (client == null) {
                    log.warn("client not found for clientId:{} service:{}", clientId, service.getGroupedServiceName());
                    continue;
                }

                Subscriber subscriber = client.getSubscriber(service);
                if (subscriber == null) {
                    log.warn("subscriber not found for clientId:{} service:{}", clientId, service.getGroupedServiceName());
                    continue;
                }

                ServicePushCallback servicePushCallback = new ServicePushCallback(clientId, subscriber, pushDataWrapper.getServiceInfo(), pushDelayTask.isPushToAll());
                pushExecutor.doPushWithCallback(clientId, subscriber, pushDataWrapper, servicePushCallback);
            }
        } catch (Exception e) {
            log.error("com.alibaba.nacos.naming.push.v2.task.PushExecuteTask.run error", e);
        }
    }

    private PushDataWrapper generatePushData() {
        ServiceInfo serviceInfo = serviceStorage.getPushData(service);
        ServiceMetadata serviceMetadata = namingMetadataManager.getServiceMetadata(service);
        return new PushDataWrapper(serviceMetadata, serviceInfo);
    }

    private Collection<String> getTargetClientIds() {
        return pushDelayTask.isPushToAll() ? clientServiceIndexesManager.getAllClientsSubscribeService(service) : pushDelayTask.getTargetClients();
    }

    @RequiredArgsConstructor
    @Setter
    @ToString
    private class ServicePushCallback
            implements NamingPushCallback {
        private final String clientId;
        private final Subscriber subscriber;
        private final ServiceInfo serviceInfo;
        private final long executeStartTime = System.currentTimeMillis();
        private final boolean isPushToAll;
        private ServiceInfo actualServiceInfo;

        @Override
        public long getTimeout() {
            return Long.MAX_VALUE;
        }

        @Override
        public void onSuccess() {
            log.info("com.alibaba.nacos.naming.push.v2.task.PushExecuteTask.ServicePushCallback.onSuccess this:{}", this);
        }

        @Override
        public void onFail(Throwable throwable) {
            log.warn("com.alibaba.nacos.naming.push.v2.task.PushExecuteTask.ServicePushCallback.onFail this:{} \n", this, throwable);
        }
    }

}
